package com.googlecode.gaelloon.routing

import collection.Seq
import java.lang.String
import collection.mutable.{Builder}
import collection.immutable.HashMap

import HttpMethod._

trait Action

class Router(patterns: Seq[(HttpMethod, String, Action)]) {

  def splitPath(path: String): List[String] = {
    if (path.isEmpty) "" :: Nil
    else {
      val idx = path.indexOf('/')
      if (idx == -1) path :: Nil
      else path.slice(0, idx) :: splitPath(path.slice(idx + 1, path.length))
    }
  }

  import scala.collection.{mutable => mutable}

  abstract class Node {
    def isLeaf: Boolean

    def children: Seq[(String, Node)]

    def action: Action

    def add(path: List[String], action: Action)

    def pd(indent: Int = 0)
  }

  class LeafNode(val action: Action) extends Node {
    def isLeaf: Boolean = true

    def children: Seq[(String, Node)] = Seq()

    def add(path: List[String], action: Action) {
      throw new UnsupportedOperationException
    }

    def pd(indent: Int) {
      print(" " * indent)
      println(action)
    }
  }

  class InnerNode extends Node {
    private val chldrn: mutable.Map[String, Node] = mutable.LinkedHashMap()

    def add(path: List[String], action: Action) {
      path match {
        case segment :: subpath => chldrn.getOrElseUpdate(segment, new InnerNode).add(subpath, action)
        case Nil => chldrn.getOrElseUpdate("", new LeafNode(action))
      }
    }

    def isLeaf: Boolean = chldrn.isEmpty

    def children: Seq[(String, Node)] = chldrn.toSeq

    def action: Action = throw new UnsupportedOperationException

    def pd(indent: Int) {
      chldrn.foreach{
        case (k, v) =>
          print(" " * indent)
          println(k)
          v.pd(indent + 1)
      }
    }
  }

  val root = new InnerNode
  patterns.foreach{
    case (method, pattern, action) => root.add(splitPath(pattern.tail), action)
  }

  import util.control.Breaks._

  abstract class PathSegment {
    def matchPath(path: List[String], paramBuilder: Builder[(String, String), Map[String, String]]): Option[(Action)]

    def pd(indent: Int = 0)
  }

  abstract class AggregativePathSegment(children: Seq[PathSegment]) extends PathSegment {
    def matchPath(path: List[String], paramBuilder: Builder[(String, String), Map[String, String]]): Option[Action]

    protected def matchSubPath(subpath: List[String], paramBuilder: Builder[(String, String), Map[String, String]]): Option[Action] = {
      var result: Option[Action] = None
      breakable{
        for (child <- children) {
          val m = child.matchPath(subpath, paramBuilder)
          if (m.isDefined) {
            result = m
            break
          }
        }
      }
      result
    }

    def pd(indent: Int = 0) {
      print(" " * indent)
      println(str)
      children.foreach{
        _.pd(indent + 1)
      }
    }

    def str: String
  }

  case class RootPathSegment(children: Seq[PathSegment]) extends AggregativePathSegment(children) {
    def matchPath(path: List[String], paramBuilder: Builder[(String, String), Map[String, String]]): Option[Action] = path match {
      case "" :: subpath => matchSubPath(subpath, paramBuilder)
      case _ => None
    }

    def str: String = "Root"
  }


  abstract class SegmentMatcher {
    def matchSegment(segment: String, paramBuilder: Builder[(String, String), Map[String, String]]): Boolean
  }

  case class StaticSegmentMatcher(value: String) extends SegmentMatcher {
    def matchSegment(segment: String, paramBuilder: Builder[(String, String), Map[String, String]]): Boolean = value.equalsIgnoreCase(segment)
  }

  case class DynamicSegmentMatcher(variable: String) extends SegmentMatcher {
    def matchSegment(segment: String, paramBuilder: Builder[(String, String), Map[String, String]]): Boolean = {
      if (segment.isEmpty) false // in case of "/"
      else {
        paramBuilder += ((variable, segment))
        true
      }
    }
  }

  case class InnerPathSegment(matcher: SegmentMatcher, children: Seq[PathSegment]) extends AggregativePathSegment(children) {
    def matchPath(path: List[String], paramBuilder: Builder[(String, String), Map[String, String]]): Option[Action] = path match {
      case segment :: subpath => {
        if (matcher.matchSegment(segment, paramBuilder)) matchSubPath(subpath, paramBuilder)
        else None
      }
      case Nil => None
    }

    def str: String = "Inner(" + matcher + ")"
  }

  case class LeafPathSegment(action: Action) extends PathSegment {
    def matchPath(path: List[String], paramBuilder: Builder[(String, String), Map[String, String]]): Option[Action] = {
      if (path == Nil) Some(action) else None
    }

    def pd(indent: Int = 0) {
      print(" " * indent)
      println("Leaf")
    }
  }

  def build(node: Node, segment: String = "ROOT"): PathSegment = {
    def createSegment(children: Seq[PathSegment]) = {
      if (segment == "ROOT") RootPathSegment(children)
      else if (node.isLeaf) LeafPathSegment(node.action)
      else if (segment.startsWith("{") && segment.endsWith("}")) InnerPathSegment(DynamicSegmentMatcher(segment.slice(1, segment.length - 1)), children)
      else InnerPathSegment(StaticSegmentMatcher(segment), children)
    }

    val children = node.children.map{
      case (cs, cn) => build(cn, cs)
    }
    createSegment(children)
  }


  val atm = build(root)

  def route(path: String): Option[(Action, Map[String, String])] = {
    val paramBuilder = HashMap.newBuilder[String, String]
    atm.matchPath(splitPath(path), paramBuilder) match {
      case Some(action) => Some((action, paramBuilder.result))
      case None => None
    }
  }
}


